/**
 * @file boards\gd32f103cb\cpu\cpu.c
 *
 * Copyright (C) 2023
 *
 * cpu.c is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author HinsShum hinsshum@qq.com
 *
 * @encoding utf-8
 */

/*---------- includes ----------*/
#include "cpu.h"
#include "options.h"
#include "gd32f10x_libopt.h"
#include <string.h>

/*---------- macro ----------*/
/*---------- type define ----------*/
/*---------- variable prototype ----------*/
extern uint32_t Image$$ER_IROM1$$Base[];

/*---------- function prototype ----------*/
/*---------- variable ----------*/
static uint32_t core_clock_cycles_per_microsecond = 0;
static uint64_t volatile __ticks = 0;
static uint16_t __suspended = 0;
static uint8_t uuid[CONFIG_CHIP_UUID_SIZE];

/*---------- function ----------*/
static void _get_uuid(void)
{
    memcpy(uuid, (void *)CONFIG_CHIP_UUID_BASE, ARRAY_SIZE(uuid));
}

static void _config_systick(void)
{
    /* set systick 1KHz */
    SysTick_Config(SystemCoreClock / 1000UL);
    NVIC_SetPriority(SysTick_IRQn, NVIC_PRIORITY_MAKE(CONFIG_PRIORITY_SYSTICK));
    core_clock_cycles_per_microsecond = SystemCoreClock / 1000000U;
}

static void _config_cpu(void)
{
    cpu_restore();
    /* Configure RCU
     *  select HXTAL(8M) as input clock source
     *  PLL = (HXTAL / 2) * 27 = 108M
     *  AHB = PLL = 108M
     *  APB1 = AHB / 2 = 54M
     *  APB2 = AHB / 1 = 108M
     */
    rcu_osci_on(RCU_HXTAL);
    while(SUCCESS != rcu_osci_stab_wait(RCU_HXTAL));
    rcu_predv0_config(RCU_PREDV0_DIV2);
    rcu_pll_config(RCU_PLLSRC_HXTAL, RCU_PLL_MUL27);
    rcu_ahb_clock_config(RCU_AHB_CKSYS_DIV1);
    rcu_apb1_clock_config(RCU_APB1_CKAHB_DIV2);
    rcu_apb2_clock_config(RCU_APB2_CKAHB_DIV1);
    /* enable pll */
    rcu_osci_on(RCU_PLL_CK);
    while(SUCCESS != rcu_osci_stab_wait(RCU_PLL_CK));
    /* switch system source clock */
    rcu_system_clock_source_config(RCU_CKSYSSRC_PLL);
    while(RCU_SCSS_PLL != rcu_system_clock_source_get());
    SystemCoreClockUpdate();
}

void cpu_config(void)
{
    _config_cpu();
    /* relocate vector table */
#ifdef VECT_TAB_SRAM
    SCB->VTOR = SRAM_BASE | ((uint32_t)Image$$ER_IROM1$$Base - SRAM_BASE);
#else
    SCB->VTOR = FLASH_BASE | ((uint32_t)Image$$ER_IROM1$$Base - FLASH_BASE);
#endif
    /* enable global irq */
    __enable_irq();
    /* Set NVIC priority group */
    NVIC_SetPriorityGrouping(NVIC_PRIGROUP_PRE4_SUB0 >> SCB_AIRCR_PRIGROUP_Pos);
    /* enable alternate function clock */
    rcu_periph_clock_enable(RCU_AF);
    /* disable jtag */
    gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE);
    /* set systick to 1KHz */
    _config_systick();
    /* get uuid */
    _get_uuid();
}

void cpu_restore(void)
{
    /* disable systick */
    SysTick->CTRL &= (~(SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk));
    /* restore RCU */
    rcu_deinit();
}

void cpu_reset(void)
{
    NVIC_SystemReset();
    for(;;){
    }
}

void cpu_get_uuid(uint8_t *pbuf, uint8_t len)
{
    if(len > ARRAY_SIZE(uuid)) {
        len = ARRAY_SIZE(uuid);
    }
    if(pbuf) {
        memcpy(pbuf, uuid, len);
    }
}

void udelay(uint32_t us)
{
    uint32_t t1 = 0, t2 = 0, delta = 0;
    uint32_t cycles = us * core_clock_cycles_per_microsecond;

    t1 = SysTick->VAL;
    while(delta < cycles) {
        t2 = SysTick->VAL;
        delta = (t2 < t1) ? (t1 - t2) : (SysTick->LOAD - t2 + t1);
    }
}

void mdelay(uint32_t ms)
{
    volatile uint32_t tmp = SysTick->CTRL;
    
    (void)tmp;
    while(ms) {
        if((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) != 0U) {
            ms--;
        }
    }
}

void tick_inc(void)
{
    __ticks++;
}

uint64_t tick_get(void)
{
    uint64_t ticks = 0;

    __enter_critical();
    ticks = __ticks;
    __exit_critical();

    return ticks;
}

uint64_t tick_get_from_isr(void)
{
    return __ticks;
}

void enter_critical(void)
{
    __disable_irq();
    __suspended++;
}

void exit_critical(void)
{
    if(__suspended) {
        __suspended--;
    }
    if(!__suspended) {
        __enable_irq();
    }
}
